package me.yricky.cbor

import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer


object CBOREncoder {
    fun encode(item:CBORItem):ByteArray{
        val bos = ByteArrayOutputStream()
        when(item){
            is CBORNull -> { bos.write(BYTE_NULL) }
            is CBORTrue -> { bos.write(BYTE_TRUE) }
            is CBORFalse -> { bos.write(BYTE_FALSE) }
            is CBORUndefined -> { bos.write(BYTE_UNDEFINED) }
            is CBORNum ->{
                if(item.value <0){
                    encodeNum(bos, H3_MAJOR_NEG_NUM,-1 - item.value)
                }else{
                    encodeNum(bos, H3_MAJOR_NUM,item.value)
                }
            }
            is CBORByteArray -> {
                encodeNum(bos, H3_MAJOR_BA, item.value.size.toLong())
                bos.write(item.value)
            }
            is CBORString -> {
                val ba = item.value.encodeToByteArray()
                encodeNum(bos, H3_MAJOR_STR, ba.size.toLong())
                bos.write(ba)
            }

            is CBORArray -> {
                encodeNum(bos, H3_MAJOR_ARR, item.value.size.toLong())
                item.value.forEach {
                    bos.write(encode(it))
                }
            }

            is CBORKv -> {
                encodeNum(bos, H3_MAJOR_KV, item.value.size.toLong())
                item.value.forEach {
                    bos.write(encode(it.first))
                    bos.write(encode(it.second))
                }
            }
            else -> {}
        }
        return bos.toByteArray()
    }

    private fun encodeNum(bos:ByteArrayOutputStream,majorTypeH3:Int,value:Long){
        if(value in (0 .. 23)){
            bos.write(majorTypeH3 or value.toInt())
        }
        if(value in (24.toLong() .. UByte.MAX_VALUE.toLong())){
            bos.write(majorTypeH3 or 24)
            bos.write(value.toInt())
        }
        if(value in ((UByte.MAX_VALUE.toLong() + 1) .. (UShort.MAX_VALUE.toLong())) ){
            bos.write(majorTypeH3 or 25)
            bos.writeShort(value.toShort())
        }
        if(value in ((UShort.MAX_VALUE.toLong() + 1) .. (UInt.MAX_VALUE.toLong())) ){
            bos.write(majorTypeH3 or 26)
            bos.writeInt(value.toInt())
        }
        if(value in ((UInt.MAX_VALUE.toLong() + 1) .. (ULong.MAX_VALUE.toLong())) ){
            bos.write(majorTypeH3 or 27)
            bos.writeLong(value)
        }
    }



    private fun ByteArrayOutputStream.writeShort(num:Short){
        write((num.toInt() ushr 8) and 0xff)
        write(num.toInt() and 0xff)
    }

    private fun ByteArrayOutputStream.writeInt(num:Int){
        write((num ushr 24) and 0xff)
        write((num ushr 16) and 0xff)
        write((num ushr 8) and 0xff)
        write(num and 0xff)
    }

    private fun ByteArrayOutputStream.writeLong(num:Long){
        write(((num ushr 56) and 0xff).toInt())
        write(((num ushr 48) and 0xff).toInt())
        write(((num ushr 40) and 0xff).toInt())
        write(((num ushr 32) and 0xff).toInt())
        write(((num ushr 24) and 0xff).toInt())
        write(((num ushr 16) and 0xff).toInt())
        write(((num ushr 8)  and 0xff).toInt())
        write((num and 0xff).toInt())
    }
}